<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>深浅拷贝</title>
</head>
<body>

<!-- 
    一、深浅拷贝的概念 

        1. 浅拷贝：先创建一个新对象，这个对象有着原始对象属性的一份精确拷贝。
        如果属性是基本类型，拷贝的就是基本类型的值，如果属性是引用类型，拷贝的就是内存地址，
        所以如果其中一个对象改变了这个地址，就会影响到另一个对象。

        2. 深拷贝：将一个对象从内存中完整的拷贝一份出来，从堆内存中开辟一个新的区域存放新对象，
        且修改新对象不会影响原对象。

    二、针对引用类型来说赋值、浅拷贝、深拷贝的区别

        1. 赋值：当我们把一个对象赋值给一个新的变量时，赋的其实是该对象的在栈中的地址，而不是堆中的数据。
        也就是两个对象指向的是同一个存储空间，无论哪个对象发生改变，其实都是改变的存储空间的内容，因此，两个对象是联动的。

        2. 浅拷贝：重新在堆中创建内存，拷贝前后对象的基本数据类型互不影响，但拷贝前后对象的引用类型因共享同一块内存，会互相影响。

        3. 深拷贝：从堆内存中开辟一个新的区域存放新对象，对对象中的子对象进行递归拷贝，拷贝前后的两个对象互不影响。

-->

    
</body>
<script>
    // 赋值
    // 基本数据类型：互不影响
    let a = 1
    let b = a
    a = 2
    console.log('赋值-基本数据类型', a, b) // 2, 1
    // 引用数据类型：互相影响
    let c = {num: 1}
    let d = c
    c.num = 2
    console.log('赋值-引用数据类型', c, d) // {num: 2} , {num: 2}

    // 浅拷贝
    function clone(obj){
        let newObj = {}
        for(let key in obj) {
            newObj[key] = obj[key]
        }
        return newObj
    }

    let person = {
        name: 'Sunny',
        hobby:['学习','唱歌','跳舞'],
        undefined: undefined,
        null: null,
        reg: new RegExp('0-9'),
        date: new Date(),
        function: function(){},
        err: new Error('error')
    }
    let person1 = clone(person)
    person1.name = 'Kitty'  // 基础数据类型修改互不影响
    console.log('浅拷贝-改变基本数据类型', person, person1)
    person1.hobby[0] = '滑冰'  // 引用数据类型修改互相影响
    console.log('浅拷贝-改变引用数据类型', person, person1)

    // 深拷贝-递归法
    function deepClone(obj) {
        if (obj === null) return obj
        if (typeof obj !== 'object') return obj
        if (obj instanceof Date) return new Date(obj)
        if (obj instanceof RegExp) return new RegExp(obj)
        // let newObj = {}
        let newObj = new obj.constructor()
        for(let key in obj){
            newObj[key] = deepClone(obj[key])
        }
        return newObj
    }
    let person2 = deepClone(person)
    person2.name = 'Honey'
    person2.hobby[0] = '跑步'
    console.log('深拷贝-递归方法', person, person2)

    // 深拷贝-JSON方法
    // JSON.stringify(), 将一个对象解析成一个字符串
    // JSON.parse(), 将一个字符串解析出json对象
    let person3 = JSON.parse(JSON.stringify(person))
    console.log('深拷贝-JSON方法', person, person3)

    /* 使用JSON法进行对象的深拷贝的弊端
        1. 如果json里有时间对象，则序列化结果，时间对象=>字符串形式；
        2. 如果json里有RegExp、Error，则序列化的结果只得到空对象，RegExp、Error => {};
        3. 如果json里有function、undefined，则序列化的结果会把它们丢失；
        4. 如果json里有NaN、Infinity和-Infinity，则序列化的结果会变成null；
        5. 如果json里有对象是由构造函数生成的，则序列化的结果会丢弃对象的constructor；
        6. 如果对象中存在循环引用的情况也无法实现深拷贝
            循环引用： 
                let obj1 = {}
                let obj2 = { a: obj1 }
                obj1.b = obj2
    */


    // 数组的拷贝方法
    let arr = [0,1,2,[0,1,2]]
    //方法1
    let arr1 = arr.slice(0)
    arr1[0] = 1
    arr1[3][0] = 1
    console.log(arr, arr1)
    //方法2
    let arr2 = arr.concat()
    arr2[0] = 2
    arr2[3][0] = 2
    console.log(arr, arr2)
    //方法3
    let arr3 = [...arr]
    arr3[0] = 3
    arr3[3][0] = 3
    console.log(arr, arr3)
    //方法4
    function copyArr(arr){
        let newArr = []
        for(let i=0; i<arr.length; i++) {
            newArr.push(arr[i])
        }
        return newArr
    }
    let arr4 = copyArr(arr)
    arr4[0] = 4
    arr4[3][0] = 4
    console.log(arr, arr4)


    /*
        一、浅拷贝的方法
        对象：Object.assign、...（扩展运算符）
        lodash中clone方法
        二、深拷贝的方法
        递归、
        JSON.parse(JSON.stringify(obj))、
        lodash中cloneDeep方法、
        Jquery中extend方法

    */


</script>
</html>